<?php
// 应用公共文件

use app\admin\model\Config as configModel;
use app\admin\model\PrizeRecord as PrizeRecordModel;
use app\common\library\Upload;
use app\common\model\AccessToken;
use app\common\model\JsapiTicket;
use app\common\model\User;
use jueqi\Random;
use Symfony\Component\HttpFoundation\IpUtils;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\exception\HttpResponseException;
use think\facade\Config;
use think\facade\Db;
use think\facade\Lang;
use think\Response;

if (!function_exists('path_is_writable')) {
    /**
     * 检查目录/文件是否可写
     * @param $path
     * @return bool
     */
    function path_is_writable($path): bool
    {
        if (DIRECTORY_SEPARATOR == '/' && !@ini_get('safe_mode')) {
            return is_writable($path);
        }

        if (is_dir($path)) {
            $path = rtrim($path, '/') . '/' . md5(mt_rand(1, 100) . mt_rand(1, 100));
            if (($fp = @fopen($path, 'ab')) === false) {
                return false;
            }

            fclose($fp);
            @chmod($path, 0777);
            @unlink($path);

            return true;
        } elseif (!is_file($path) || ($fp = @fopen($path, 'ab')) === false) {
            return false;
        }

        fclose($fp);
        return true;
    }
}

if (!function_exists('deldir')) {

    /**
     * 删除文件夹
     * @param string $dirname 目录
     * @param bool $delself 是否删除自身
     * @return boolean
     */
    function deldir(string $dirname, bool $delself = true): bool
    {
        if (!is_dir($dirname)) {
            return false;
        }
        $files = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($dirname, FilesystemIterator::SKIP_DOTS),
            RecursiveIteratorIterator::CHILD_FIRST
        );

        foreach ($files as $fileinfo) {
            if ($fileinfo->isDir()) {
                deldir($fileinfo->getRealPath());
            } else {
                @unlink($fileinfo->getRealPath());
            }
        }
        if ($delself) {
            @rmdir($dirname);
        }
        return true;
    }
}

if (!function_exists('del_empty_dir')) {
    /**
     * 删除一个路径下的所有相对空文件夹（删除此路径中的所有空文件夹）
     * @param string $path 相对于根目录的文件夹路径，如 c:BuildAdmin/a/b/
     * @return void
     */
    function del_empty_dir(string $path)
    {
        $path = str_replace(root_path(), '', rtrim(path_transform($path), DIRECTORY_SEPARATOR));
        $path = array_filter(explode(DIRECTORY_SEPARATOR, $path));
        for ($i = count($path) - 1; $i >= 0; $i--) {
            $dirPath = root_path() . implode(DIRECTORY_SEPARATOR, $path);
            if (!is_dir($dirPath)) {
                unset($path[$i]);
                continue;
            }
            if (dir_is_empty($dirPath)) {
                deldir($dirPath);
                unset($path[$i]);
            } else {
                break;
            }
        }
    }
}

if (!function_exists('dir_is_empty')) {
    function dir_is_empty(string $dir): bool
    {
        $handle = opendir($dir);
        while (false !== ($entry = readdir($handle))) {
            if ($entry != "." && $entry != "..") {
                closedir($handle);
                return false;
            }
        }
        closedir($handle);
        return true;
    }
}

if (!function_exists('__')) {
    /**
     * 语言翻译
     * @param string $name 被翻译字符
     * @param array $vars 替换字符数组
     * @param string $lang 翻译语言
     * @return string
     */
    function __(string $name, array $vars = [], string $lang = '')
    {
        if (is_numeric($name) || !$name) {
            return $name;
        }
        return Lang::get($name, $vars, $lang);
    }
}

if (!function_exists('get_sys_config')) {
    /**
     * 获取站点的系统配置，不传递参数则获取所有配置项
     * @param string $name 变量名
     * @param string $group 变量分组，传递此参数来获取某个分组的所有配置项
     * @param bool $reduct 是否开启简洁模式，简洁模式下，获取多项配置时只返回配置的键值对
     * @return string | array
     */
    function get_sys_config(string $name = '', string $group = '', bool $reduct = true)
    {
        try {
            if ($name) {
                // 直接使用->value('value')不能使用到模型的类型格式化
                $config = (new app\admin\model\Config)->cache($name, null, configModel::$cacheTag)->where('name', $name)->find();
                if ($config) $config = $config['value'];
            } else {
                if ($group) {
                    $temp = (new app\admin\model\Config)->cache('group' . $group, null, configModel::$cacheTag)->where('group', $group)->select()->toArray();
                } else {
                    $temp = (new app\admin\model\Config)->cache('sys_config_all', null, configModel::$cacheTag)->order('weigh desc')->select()->toArray();
                }
                if ($reduct) {
                    $config = [];
                    foreach ($temp as $item) {
                        $config[$item['name']] = $item['value'];
                    }
                } else {
                    $config = $temp;
                }
            }
        } catch (DataNotFoundException|DbException|ModelNotFoundException $e) {
            return [];
        }
        return $config;
    }
}

if (!function_exists('get_route_remark')) {
    /**
     * 获取当前路由后台菜单规则的备注信息
     * @return string
     */
    function get_route_remark()
    {
        $db             = new Db();
        $controllerName = request()->controller(true);
        $actionName     = request()->action(true);
        $path           = str_replace('.', '/', $controllerName);

        $remark = $db::name('menu_rule')
            ->where('name', $path)
            ->whereOr('name', $path . '/' . $actionName)
            ->value('remark');
        return __((string)$remark);
    }
}

if (!function_exists('get_cdn_url')) {
    /**
     * 获取当前路由后台菜单规则的备注信息
     * @return string
     */
    function get_cdn_url(): string
    {
        $cdnUrl = '';
        // 远程附件前缀
        $storageConfig = get_sys_config("storage_config");
        if ($storageConfig !== 'local') {
            if ($storageConfig === 'cos') {
                // 腾讯云配置
                $cosConfig = get_sys_config("", 'cos');
                if (empty($cosConfig)) {
                    return $cdnUrl;
                }
                $cos_url = $cosConfig['cos_url'] ?? ''; // 自定义URL
                $bucket  = $cosConfig['cos_bucket'] ?? ''; // 存储桶
                $region  = $cosConfig['cos_region'] ?? ''; // 地域
                if (empty($cos_url)) {
                    $cdnUrl = 'https://' . $bucket . '.cos.' . $region . '.myqcloud.com';
                } else {
                    $cdnUrl = $cos_url;
                }
            } else if ($storageConfig === 'oss') {
                // 阿里云配置
                $ossConfig = get_sys_config("", 'oss');
                if (empty($ossConfig)) {
                    return $cdnUrl;
                }
                $bucket   = $ossConfig['oss_bucket'] ?? ''; // 存储桶
                $endPoint = $ossConfig['oss_endpoint'] ?? ''; // EndPoint
                if (empty($cos_url)) {
                    $cdnUrl = 'https://' . $bucket . '.' . $endPoint;
                } else {
                    $cdnUrl = $cos_url;
                }
            }
        }else{
            $path   = "/addons/jueqi_survey_pro/public/";
            $cdnUrl = $path;
        }
        return rtrim($cdnUrl, '/');
    }
}

if (!function_exists('full_url')) {
    /**
     * 获取资源完整url地址
     * @param string $relativeUrl 资源相对地址 不传入则获取域名
     * @param boolean $domain 是否携带域名 或者直接传入域名
     * @return string
     */
    function full_url(string $relativeUrl = '', bool $domain = true, $default = '')
    {
        $cdnUrl = Config::get('backstage.cdn_url');
        if (!$cdnUrl) $cdnUrl = request()->upload['cdn'] ?? request()->domain();
        if ($domain === true) {
            $domain = $cdnUrl;
        } elseif ($domain === false) {
            $domain = '';
        }

        $relativeUrl = $relativeUrl ?: $default;
        if (!$relativeUrl) return $domain;

        $regex = "/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i";
        if (preg_match('/^http(s)?:\/\//', $relativeUrl) || preg_match($regex, $relativeUrl) || $domain === false) {
            return $relativeUrl;
        }
        return $domain . $relativeUrl;
    }
}

if (!function_exists('encrypt_password')) {
    /**
     * 加密密码
     */
    function encrypt_password($password, $salt = '', $encrypt = 'md5')
    {
        return $encrypt($encrypt($password) . $salt);
    }
}

if (!function_exists('str_attr_to_array')) {
    /**
     * 将字符串属性列表转为数组
     * @param string $attr 属性，一行一个，无需引号，比如：class=input-class
     * @return array
     */
    function str_attr_to_array(string $attr): array
    {
        if (!$attr) return [];
        $attr     = explode("\n", trim(str_replace("\r\n", "\n", $attr)));
        $attrTemp = [];
        foreach ($attr as $item) {
            $item = explode('=', $item);
            if (isset($item[0]) && isset($item[1])) {
                $attrVal = $item[1];
                if ($item[1] === 'false' || $item[1] === 'true') {
                    $attrVal = !($item[1] === 'false');
                } elseif (is_numeric($item[1])) {
                    $attrVal = (float)$item[1];
                }
                if (strpos($item[0], '.')) {
                    $attrKey = explode('.', $item[0]);
                    if (isset($attrKey[0]) && isset($attrKey[1])) {
                        $attrTemp[$attrKey[0]][$attrKey[1]] = $attrVal;
                        continue;
                    }
                }
                $attrTemp[$item[0]] = $attrVal;
            }
        }
        return $attrTemp;
    }
}

if (!function_exists('action_in_arr')) {
    /**
     * 检测一个方法是否在传递的数组内
     * @param array|string $arr
     * @return bool
     */
    function action_in_arr($arr = []): bool
    {
        $arr = is_array($arr) ? $arr : explode(',', $arr);
        if (!$arr) {
            return false;
        }
        $arr = array_map('strtolower', $arr);
        if (in_array(strtolower(request()->action()), $arr) || in_array('*', $arr)) {
            return true;
        }
        return false;
    }
}

if (!function_exists('build_suffix_svg')) {
    /**
     * 构建文件后缀的svg图片
     * @param string $suffix 文件后缀
     * @param string|null $background 背景颜色，如：rgb(255,255,255)
     * @return string
     */
    function build_suffix_svg(string $suffix = 'file', string $background = null): string
    {
        $suffix = mb_substr(strtoupper($suffix), 0, 4);
        $total  = unpack('L', hash('adler32', $suffix, true))[1];
        $hue    = $total % 360;
        [$r, $g, $b] = hsv2rgb($hue / 360, 0.3, 0.9);

        $background = $background ?: "rgb($r,$g,$b)";

        return '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
            <path style="fill:#E2E5E7;" d="M128,0c-17.6,0-32,14.4-32,32v448c0,17.6,14.4,32,32,32h320c17.6,0,32-14.4,32-32V128L352,0H128z"/>
            <path style="fill:#B0B7BD;" d="M384,128h96L352,0v96C352,113.6,366.4,128,384,128z"/>
            <polygon style="fill:#CAD1D8;" points="480,224 384,128 480,128 "/>
            <path style="fill:' . $background . ';" d="M416,416c0,8.8-7.2,16-16,16H48c-8.8,0-16-7.2-16-16V256c0-8.8,7.2-16,16-16h352c8.8,0,16,7.2,16,16 V416z"/>
            <path style="fill:#CAD1D8;" d="M400,432H96v16h304c8.8,0,16-7.2,16-16v-16C416,424.8,408.8,432,400,432z"/>
            <g><text><tspan x="220" y="380" font-size="124" font-family="Verdana, Helvetica, Arial, sans-serif" fill="white" text-anchor="middle">' . $suffix . '</tspan></text></g>
        </svg>';
    }
}

if (!function_exists('get_area')) {
    function get_area()
    {
        $db = new Db();
        try {
            $province = request()->get('province', '');
            $city     = request()->get('city', '');
            $where    = ['pid' => 0, 'level' => 1];
            if ($province !== '') {
                $where['pid']   = $province;
                $where['level'] = 2;
                if ($city !== '') {
                    $where['pid']   = $city;
                    $where['level'] = 3;
                }
            }
            return $db::name('area')->where($where)->field('id as value,name as label')->select();
        } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
            return [];
        }
    }
}

if (!function_exists('hsv2rgb')) {
    function hsv2rgb($h, $s, $v): array
    {
        $r = $g = $b = 0;

        $i = floor($h * 6);
        $f = $h * 6 - $i;
        $p = $v * (1 - $s);
        $q = $v * (1 - $f * $s);
        $t = $v * (1 - (1 - $f) * $s);

        switch ($i % 6) {
            case 0:
                $r = $v;
                $g = $t;
                $b = $p;
                break;
            case 1:
                $r = $q;
                $g = $v;
                $b = $p;
                break;
            case 2:
                $r = $p;
                $g = $v;
                $b = $t;
                break;
            case 3:
                $r = $p;
                $g = $q;
                $b = $v;
                break;
            case 4:
                $r = $t;
                $g = $p;
                $b = $v;
                break;
            case 5:
                $r = $v;
                $g = $p;
                $b = $q;
                break;
        }

        return [
            floor($r * 255),
            floor($g * 255),
            floor($b * 255)
        ];
    }
}

if (!function_exists('ip_check')) {
    function ip_check($ip = null)
    {
        $ip       = is_null($ip) ? getClientIp() : $ip;
        $noAccess = get_sys_config('no_access_ip');
        $noAccess = !$noAccess ? [] : array_filter(explode("\n", str_replace("\r\n", "\n", $noAccess)));
        if ($noAccess && IpUtils::checkIp($ip, $noAccess)) {
            $response = Response::create(['msg' => 'No permission request'], 'json', 403);
            throw new HttpResponseException($response);
        }
    }
}

if (!function_exists('set_timezone')) {
    function set_timezone($timezone = null)
    {
        $defaultTimezone = Config::get('app.default_timezone');
        $timezone        = is_null($timezone) ? get_sys_config('time_zone') : $timezone;
        if ($timezone && $defaultTimezone != $timezone) {
            Config::set([
                'app.default_timezone' => $timezone
            ]);
            date_default_timezone_set($timezone);
        }
    }
}

if (!function_exists('path_transform')) {
    function path_transform(string $path)
    {
        return str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
    }
}

if (!function_exists('get_upload_config')) {
    /**
     * 获取文件上传参数配置
     * @return array
     */
    function get_upload_config(): array
    {
        $uploadConfig = Config::get('upload');
        if (empty($uploadConfig)) {
            $uploadConfig['maxsize'] = '10mb';
        }
        $uploadConfig['maxsize'] = file_unit_to_byte($uploadConfig['maxsize']);

        $upload = request()->upload;
        if (!$upload) {
            $uploadConfig['mode'] = 'local';
            return $uploadConfig;
        }
        unset($upload['cdn']);
        return array_merge($upload, $uploadConfig);
    }
}

if (!function_exists('file_unit_to_byte')) {
    /**
     * 将一个文件单位转为字节
     * @param string $unit 将b、kb、m、mb、g、gb的单位转为 byte
     */
    function file_unit_to_byte(string $unit): int
    {
        preg_match('/([0-9\.]+)(\w+)/', $unit, $matches);
        if (!$matches) {
            return 0;
        }
        $typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
        return (int)($matches[1] * pow(1024, $typeDict[strtolower($matches[2])] ?? 0));
    }
}

if (!function_exists('stringToBoolean')) {
    /**
     * 把字符串true/false转换为Boolean
     */
    function stringToBoolean($val, $return_null = false)
    {
        $boolVal = (is_string($val) ? filter_var($val, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) : (bool)$val);
        return ($boolVal === null && !$return_null ? false : $boolVal);
    }
}

if (!function_exists('getDictionaryTypeField')) {
    /**
     * 获取系统配置里有字典属性的字段
     */
    function getDictionaryTypeField(): array
    {
        return ['array', 'radio', 'checkbox', 'select', 'selects'];
    }
}

if (!function_exists('verifyMode')) {
    /**
     * 正则验证方法
     */
    function verifyMode(): array
    {
        return [
            [
                'name'  => "account",
                'desc'  => '姓名',
                'regex' => '/^[\x{4e00}-\x{9fa5}A-Za-z0-9_-]+$/u',
            ],
            [
                'name'  => "mobile",
                'desc'  => "手机号",
                'regex' => "/^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/",
            ],
            [
                'name'  => "email",
                'desc'  => "邮箱",
                'regex' => "/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$/",
            ],
            [
                'name'  => "required",
                'desc'  => "必填",
                'regex' => "",
            ],
            [
                'name'  => "int",
                'desc'  => "整数",
                'regex' => "/^(0|[1-9][0-9]*|-[1-9][0-9]*)$/",
            ],
            [
                'name'  => "float",
                'desc'  => "浮点数",
                'regex' => "/^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$/",
            ],
            [
                'name'  => "amount",
                'desc'  => "金额",
                'regex' => "/^([0-9]+|[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$/",
            ],
            [
                'name'  => "idCard",
                'desc'  => "身份证",
                'regex' => "/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)$/",
            ],
            [
                'name'  => "phone",
                'desc'  => "电话号码",
                'regex' => "/^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$/",
            ],
            [
                'name'  => 'number',
                'desc'  => '数字',
                'regex' => "/^[0-9]*$/",
            ],
            [
                'name'  => 'url',
                'desc'  => 'URL',
                'regex' => "/^(http|https):\/\/([\w\-]+(\.[\w\-]+)*\/)*[\w\-]+(\.[\w\-]+)*\/?(\?([\w\-\.,@?^=%&:\/~\+#]*)+)?/"
            ],
            [
                'name'  => 'date',
                'desc'  => '日期',
                'regex' => "/^\d{4}-\d{1,2}-\d{1,2}/"
            ],
            [
                'name'  => 'account',
                'desc'  => '账号',
                'regex' => '/^[a-zA-Z0-9_\x80-\xff\s·]+$/'
            ],
            [
                'name'  => 'password',
                'desc'  => '密码',
                'regex' => '/^(?!.*[&<>"\'\n\r]).{6,32}$/'
            ],
            [
                'name'  => 'json',
                'desc'  => 'JSON',
                'regex' => '/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/',
            ],
            [
                'name'  => "integer",
                'desc'  => "正整数",
                'regex' => "/^([1-9][0-9]*)$/",
            ],
            [
                'name'  => "varName",
                'desc'  => "变量名",
                'regex' => "/^[a-zA-Z_][a-zA-Z0-9_]*$/",
            ],
        ];
    }
}

if (!function_exists('verifyModeMap')) {
    /**
     * 正则验证方法
     */
    function verifyModeMap(): array
    {
        $list   = verifyMode();
        $result = array();
        foreach ($list as $val) {
            $name          = $val['name'];
            $result[$name] = $val['regex'];
        }
        return $result;
    }
}

if (!function_exists('variableTypeSelect')) {
    /**
     * 变量类型下拉选择框接口
     */
    function variableTypeSelect(): array
    {
        return [
            [
                'name' => 'string',
                'desc' => "字符串",
            ],
            [
                'name' => 'password',
                'desc' => "密码",
            ],
            [
                'name' => 'number',
                'desc' => "数字",
            ],
            [
                'name' => 'radio',
                'desc' => "单选",
            ],
            [
                'name' => 'checkbox',
                'desc' => "多选",
            ],
            [
                'name' => 'switch',
                'desc' => "开关",
            ],
            [
                'name' => 'textarea',
                'desc' => "文本域",
            ],
            [
                'name' => 'array',
                'desc' => "数组",
            ],
            [
                'name' => 'datetime',
                'desc' => "年月日时分秒",
            ],
            [
                'name' => 'date',
                'desc' => "年月日",
            ],
            [
                'name' => 'time',
                'desc' => "时分秒",
            ],
            [
                'name' => 'year',
                'desc' => "年份",
            ],
            [
                'name' => 'month',
                'desc' => "月份",
            ],
            [
                'name' => 'hour',
                'desc' => "时",
            ],
            [
                'name' => 'minute',
                'desc' => "时分",
            ],
            [
                'name' => 'select',
                'desc' => "单选下拉框",
            ],
            [
                'name' => 'xm-select',
                'desc' => "XM单选下拉框",
            ],
            [
                'name' => 'xm-selects',
                'desc' => "XM多选下拉框",
            ],
            [
                'name' => 'editor',
                'desc' => "富文本",
            ],
            [
                'name' => 'city',
                'desc' => "城市",
            ],
            [
                'name' => 'image',
                'desc' => "单图上传",
            ],
            [
                'name' => 'images',
                'desc' => "多图上传",
            ],
            [
                'name' => 'file',
                'desc' => "单文件上传",
            ],
            [
                'name' => 'files',
                'desc' => "多文件上传",
            ],
            [
                'name' => 'color',
                'desc' => "颜色",
            ],
            [
                'name' => 'slider-range',
                'desc' => '滑块范围',
            ],
        ];
    }

    if (!function_exists('timestampToTime')) {
        /**
         * 时间戳转换为日期
         * @param string $timestamp // 时间戳
         * @param string $format // 格式
         * @return false|string
         */
        function timestampToTime(string $timestamp = '', string $format = '')
        {
            if (empty($timestamp) || is_numeric($timestamp) === false) {
                return $timestamp;
            }
            if (empty($format)) {
                $format = "Y-m-d H:i:s";
            }
            return date($format, $timestamp);
        }
    }

    if (!function_exists('dateToTimestamp')) {
        /**
         * 日期转换为时间戳
         * @param string $date // 日期
         * @return int|string
         */
        function dateToTimestamp(string $date = '')
        {
            $res = strtotime($date);
            if ($res) {
                return $res;
            }
            return $date;
        }
    }

    if (!function_exists('isEmpty')) {
        /**
         * 判断数据是否为空
         * @param null $value // 要判断的值
         * @param bool $zeroIsEmpty // 0是否也判断为空：true-判断为空（默认），false-判断不为空
         * @return bool
         */
        function isEmpty($value = null, bool $zeroIsEmpty = false): bool
        {
            if (is_null($value)) {
                return true;
            } else if (is_numeric($value)) {
                if ($zeroIsEmpty) {
                    return 0 == $value; // ‘0’认为是空
                } else {
                    return 0 != $value && !$value; // ‘0’不认为是空
                }
            } else if (is_string($value)) {
                return 0 == strlen($value);
            } else if (is_array($value)) {
                return 0 == count($value);
            } else if (is_bool($value)) {
                return !$value;
            } else if (empty($value)) {
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * 发送curl请求
     */
    if (!function_exists('curlRequest')) {
        /**
         * 使用curl方式实现get或post请求
         * @param $url // 请求的url地址
         * @param $data // 发送的post数据 如果为空则为get方式请求
         * return 请求后获取到的数据
         */
        function curlRequest($url, $data = '')
        {
            $ch                             = curl_init();
            $params[CURLOPT_URL]            = $url;    //请求url地址
            $params[CURLOPT_HEADER]         = false; //是否返回响应头信息
            $params[CURLOPT_RETURNTRANSFER] = true; //是否将结果返回
            $params[CURLOPT_FOLLOWLOCATION] = true; //是否重定向
            $params[CURLOPT_TIMEOUT]        = 30; //超时时间
            if (!empty($data)) {
                $params[CURLOPT_POST]       = true;
                $params[CURLOPT_POSTFIELDS] = $data;
            }
            $params[CURLOPT_SSL_VERIFYPEER] = false;//请求https时设置,还有其他解决方案
            $params[CURLOPT_SSL_VERIFYHOST] = false;//请求https时,其他方案查看其他博文
            curl_setopt_array($ch, $params); //传入curl参数
            $content = curl_exec($ch); //执行
            curl_close($ch); //关闭连接
            return $content;
        }
    }

    /**
     * 判断当前环境
     */
    if (!function_exists('getSource')) {
        /**
         * 获取来源
         * @return string
         */
        function getSource(): string
        {
            $userAgent = strtolower($_SERVER['HTTP_USER_AGENT']);
            if (strpos($userAgent, "micromessenger")) {
                if (strpos($userAgent, "miniprogram")) {
                    return 'xcx';
                } else {
                    return 'wechat';
                }
            }
            return 'h5';
        }
    }

    if (!function_exists('checkMobile')) {
        /**
         * 判断是否是手机端
         * 根据php的$_SERVER['HTTP_USER_AGENT'] 中各种浏览器访问时所包含各个浏览器特定的字符串来判断是属于PC还是移动端
         * @return  BOOL
         */
        function checkMobile(): bool
        {
            $_SERVER['ALL_HTTP'] = $_SERVER['ALL_HTTP'] ?? '';
            $mobile_browser      = '0';
            if (preg_match('/(up.browser|up.link|mmp|symbian|smartphone|midp|wap|phone|iphone|ipad|ipod|android|xoom)/i', strtolower($_SERVER['HTTP_USER_AGENT'])))
                $mobile_browser++;
            if ((isset($_SERVER['HTTP_ACCEPT'])) and (strpos(strtolower($_SERVER['HTTP_ACCEPT']), 'application/vnd.wap.xhtml+xml') !== false))
                $mobile_browser++;
            if (isset($_SERVER['HTTP_X_WAP_PROFILE']))
                $mobile_browser++;
            if (isset($_SERVER['HTTP_PROFILE']))
                $mobile_browser++;
            $mobile_ua     = strtolower(substr($_SERVER['HTTP_USER_AGENT'], 0, 4));
            $mobile_agents = array(
                'w3c ', 'acs-', 'alav', 'alca', 'amoi', 'audi', 'avan', 'benq', 'bird', 'blac',
                'blaz', 'brew', 'cell', 'cldc', 'cmd-', 'dang', 'doco', 'eric', 'hipt', 'inno',
                'ipaq', 'java', 'jigs', 'kddi', 'keji', 'leno', 'lg-c', 'lg-d', 'lg-g', 'lge-',
                'maui', 'maxo', 'midp', 'mits', 'mmef', 'mobi', 'mot-', 'moto', 'mwbp', 'nec-',
                'newt', 'noki', 'oper', 'palm', 'pana', 'pant', 'phil', 'play', 'port', 'prox',
                'qwap', 'sage', 'sams', 'sany', 'sch-', 'sec-', 'send', 'seri', 'sgh-', 'shar',
                'sie-', 'siem', 'smal', 'smar', 'sony', 'sph-', 'symb', 't-mo', 'teli', 'tim-',
                'tosh', 'tsm-', 'upg1', 'upsi', 'vk-v', 'voda', 'wap-', 'wapa', 'wapi', 'wapp',
                'wapr', 'webc', 'winw', 'winw', 'xda', 'xda-'
            );
            if (in_array($mobile_ua, $mobile_agents))
                $mobile_browser++;
            if (strpos(strtolower($_SERVER['ALL_HTTP']), 'operamini') !== false)
                $mobile_browser++;
            // Pre-final check to reset everything if the user is on Windows
            if (strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'windows') !== false)
                $mobile_browser = 0;
            // But WP7 is also Windows, with a slightly different characteristic
            if (strpos(strtolower($_SERVER['HTTP_USER_AGENT']), 'windows phone') !== false)
                $mobile_browser++;
            if ($mobile_browser > 0) {
                return true;
            } else {
                return false;
            }
        }
    }
    if (!function_exists('getClientIp')) {
        /**
         * 获取客户端真实IP
         * @return mixed
         */
        function getClientIp()
        {
            //判断服务器是否允许$_SERVER
            if (isset($_SERVER)) {
                if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
                    $realIp = $_SERVER["HTTP_X_FORWARDED_FOR"];
                } elseif (isset($_SERVER["HTTP_CLIENT_IP"])) {
                    $realIp = $_SERVER["HTTP_CLIENT_IP"];
                } else {
                    $realIp = $_SERVER["REMOTE_ADDR"];
                }
            } else {
                //不允许就使用getenv获取
                if (getenv("HTTP_X_FORWARDED_FOR")) {
                    $realIp = getenv("HTTP_X_FORWARDED_FOR");
                } elseif (getenv("HTTP_CLIENT_IP")) {
                    $realIp = getenv("HTTP_CLIENT_IP");
                } else {
                    $realIp = getenv("REMOTE_ADDR");
                }
            }
            return $realIp;
        }
    }
    if (!function_exists('getAccessToken')) {
        /**
         * 获取access_token
         */
        function getAccessToken()
        {
            // 查询当前是否有未过期的access_token
            $model = new AccessToken();
            try {
                $where[] = ['expire_time', '>=', time()];
                $where[] = ['type', '=', 1];
                $info    = $model->field("access_token")->where($where)->find();
            } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
                $info = array();
            }
            if (!empty($info->access_token)) {
                return $info->access_token;
            }
            $wxConfig  = get_sys_config("", 'wx_config');
            $appid     = $wxConfig['appid'] ?? '';
            $appSecret = $wxConfig['app_secret'] ?? '';
            $url       = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$appSecret";
            $result    = curlRequest($url);
            $bodyData  = json_decode($result, true);
            if (!is_array($bodyData)) {
                return false;
            }
            if (!isset($bodyData['access_token']) || !isset($bodyData['expires_in'])) {
                return false;
            }
            $accessToken = $bodyData['access_token']; // access_token
            $expiresIn   = $bodyData['expires_in']; // 过期时间(秒)
            if (empty($expiresIn)) {
                $expiresIn = 7200;
            }
            $expireTime = time() + $expiresIn;
            $data       = array(
                'access_token' => $accessToken,
                'expire_time'  => $expireTime,
                'type'         => 1,
            );
            $model->save($data);
            return $accessToken;
        }
    }

    if (!function_exists('getJsApiTicket')) {
        /**
         * 获取jsapi_ticket
         */
        function getJsApiTicket()
        {
            // 查询当前是否有未过期的jsapi_ticket
            $model = new JsApiTicket();
            try {
                $where[] = ['expire_time', '>=', time()];
                $where[] = ['type', '=', 1];
                $info    = $model->field("jsapi_ticket")->where($where)->find();
            } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
                $info = array();
            }
            if (!empty($info->jsapi_ticket)) {
                return $info->jsapi_ticket;
            }
            $accessToken = getAccessToken();
            $url         = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=$accessToken&type=jsapi";
            $result      = curlRequest($url);
            $bodyData    = json_decode($result, true);
            if (!is_array($bodyData)) {
                return false;
            }
            if (!isset($bodyData['errcode'])) {
                return false;
            }
            if ($bodyData['errcode'] !== 0 && $bodyData['errcode'] !== '0') {
                return false;
            }
            $jsapiTicket = $bodyData['ticket']; // jsapi_ticket
            $expiresIn   = $bodyData['expires_in']; // 过期时间(秒)
            if (empty($expiresIn)) {
                $expiresIn = 7200;
            }
            $expireTime = time() + $expiresIn;
            $data       = array(
                'jsapi_ticket' => $jsapiTicket,
                'expire_time'  => $expireTime
            );
            $model->save($data);
            return $jsapiTicket;
        }
    }
    if (!function_exists('generateSignature')) {
        /**
         * 生成签名
         */
        function generateSignature($webUrl = ''): array
        {
            $jsapiTicket = getJsApiTicket();
            $params      = array(
                'noncestr'     => Random::build('alnum', 16),
                'jsapi_ticket' => $jsapiTicket,
                'timestamp'    => time(),
                'url'          => $webUrl,
            );
            $signature   = sha1(ASCIISort($params));
            unset($params['jsapi_ticket']);
            $params['signature'] = $signature;
            return $params;
        }
    }
    if (!function_exists('ASCIISort')) {
        /**
         * 按照ASCII排序
         */
        function ASCIISort($params = array(), $notSortField = array()): string
        {
            // ksort()对数组按照键名进行升序排序
            ksort($params);
            // reset()内部指针指向数组中的第一个元素
            $sign = '';
            foreach ($params as $key => $val) { //遍历POST参数
                if (isEmpty($val) || in_array($key, $notSortField)) {
                    continue; //跳过这些不签名
                }
                // 第一个字符串签名不加& 其他加&连接起来参数
                if ($sign) {
                    $sign .= '&';
                }
                // 拼接为url参数形式
                $sign .= "$key=$val";
            }
            return $sign;
        }
    }
    if (!function_exists('getURL')) {
        /**
         * 完整url链接
         * @return string
         */
        function getURL(): string
        {
            $http_url = isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == '443' ? 'https://' : 'http://';
            $http_url .= $_SERVER['HTTP_HOST'] ?? $_SERVER["SERVER_NAME"];
            if (isset($_SERVER['REQUEST_URI'])) {
                $http_url .= $_SERVER['REQUEST_URI'];
            } else {
                $http_url .= $_SERVER['PHP_SELF'] ?? $_SERVER['SCRIPT_NAME'];
                $http_url .= $_SERVER['QUERY_STRING'] ?? $_SERVER['PATH_INFO'] ?? '';
            }
            return $http_url;
        }
    }
    if (!function_exists('getWeChatShare')) {
        /**
         * 获取微信分享参数
         */
        function getWeChatShare($webUrl = ''): array
        {
            $params          = generateSignature($webUrl);
            $appid           = get_sys_config("appid");
            $params['appid'] = $appid;
            return $params;
        }
    }
    if (!function_exists('getHttp')) {
        /**
         * 获取协议
         */
        function getHttp(): string
        {
            if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 1) {  //Apache
                return 'https';
            } elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') { //IIS
                return 'https';
            } elseif (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) { //其他
                return 'https';
            } else {
                return 'http';
            }
        }
    }
    if (!function_exists('checkRemoteFile')) {
        /**
         * 判断远程文件是否存在
         * @param $url
         * @return bool
         */
        function checkRemoteFile($url): bool
        {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_NOBODY, 1);
            curl_setopt($ch, CURLOPT_FAILONERROR, 1);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            if (curl_exec($ch) !== FALSE) {
                return true;
            } else {
                return false;
            }
        }
    }

    if (!function_exists('getCurlFile')) {
        /**
         * 获取远程文件
         * @param $url // url
         * @param array $header 请求头
         * @return bool|string
         */
        function getCurlFile($url, array $header = [])
        {
            $curl = curl_init();
            curl_setopt($curl, CURLOPT_URL, $url);
            curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
            curl_setopt($curl, CURLOPT_HEADER, false);
            curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
            $data = curl_exec($curl);
            // 获取请求头
            $headers   = curl_getinfo($curl);
            $http_code = $headers['http_code']; // 状态码
            if (curl_error($curl)) {
                curl_close($curl);
                return false;
            } else {
                // 打印返回的内容
                curl_close($curl);
                if ($http_code != 200 || empty($data)) {
                    return false;
                }
                return $data;
            }
        }
    }

    if (!function_exists('curlWeChatPost')) {
        function curlWeChatPost($url = '', $postData = '', $options = array())
        {
            if (is_array($postData)) {
                $postData = http_build_query($postData);
            }
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
            curl_setopt($ch, CURLOPT_TIMEOUT, 30); //设置cURL允许执行的最长秒数
            if (!empty($options)) {
                curl_setopt_array($ch, $options);
            }
            //https请求 不验证证书和host
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
            //第一种方法，cert 与 key 分别属于两个.pem文件
            //默认格式为PEM，可以注释
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLCERT, dirname(__FILE__, 2) . '/wechat/cert/apiclient_cert.pem');
            //默认格式为PEM，可以注释
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLKEY, dirname(__FILE__, 2) . '/wechat/cert/apiclient_key.pem');
            //第二种方式，两个文件合成一个.pem文件
            $data = curl_exec($ch);
            curl_close($ch);
            return $data;
        }
    }

    if (!function_exists('arrayToXml')) {
        /**
         * 数组转xml
         * @param array $arr
         * @return string
         */
        function arrayToXml(array $arr = []): string
        {
            if (empty($arr)) {
                return '';
            }
            $xml = "<xml>";
            foreach ($arr as $key => $val) {
                if (is_numeric($val)) {
                    $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
                } else
                    $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
            $xml .= "</xml>";
            return $xml;
        }
    }

    if (!function_exists('grantPrize')) {
        /**
         * 发放奖品
         * @param prizeRecordModel $row // 奖品对象
         * @param int $type // 发放类型(1、自动发放2、手动发放)
         * @param string $msg // 错误信息
         * @return bool
         */
        function grantPrize(PrizeRecordModel $row, int $type = 2, string &$msg = ''): bool
        {
            if (empty($row)) {
                $msg = "数据不存在！";
                return false;
            }
            $cash        = $row['cash'] ?? 0; // 金额
            $userId      = $row['user_id'] ?? 0; // 用户ID
            $status      = (int)$row['status'] ?? 0; // 发放状态(-1、未中奖1、未发放2、已发放3、发放失败)
            $orderNumber = $row['order_number'] ?? ''; // 订单编号
            if (empty($cash) || $cash <= 0) {
                $msg = "金额错误！";
                return false;
            }
            if (empty($userId)) {
                $msg = "用户异常！";
                return false;
            }
            if (empty($orderNumber)) {
                $msg = "奖品数据异常！";
                return false;
            }
            if ($status === 2) {
                $msg = "奖品已发放！";
                return false;
            }
            if ($status === -1) {
                $msg = "未中奖！";
                return false;
            }
            if ($status !== 1 && $status !== 3) {
                $msg = "奖品已发放或奖品数据异常！";
                return false;
            }

            // 查询用户信息
            try {
                $user = (new User())->field("openid")->find($userId);
            } catch (DataNotFoundException|ModelNotFoundException|DbException $e) {
                $user = array();
            }
            if (empty($user)) {
                $msg = "用户不存在！";
                return false;
            }
            $openid = $user->openid ?? ''; // openid
            if (empty($openid)) {
                $msg = "openid为空！";
                return false;
            }
            // 查询微信参数
            $wxConfig = get_sys_config("", 'wx_config');
            if (empty($wxConfig)) {
                $msg = "微信参数未配置！";
                return false;
            }
            $appid  = $wxConfig['appid'] ?? ''; // 微信公众号appid
            $mchId  = $wxConfig['mch_id'] ?? ''; // 商户号
            $payKey = $wxConfig['pay_key'] ?? ''; // 支付密钥

            // 查询红包发放参数
            $basicsConfig = get_sys_config("", 'basics_config');
            if (empty($basicsConfig)) {
                $msg = "基础参数未配置！";
                return false;
            }
            $sendName       = $basicsConfig['send_name'] ?? ''; // 商户名称
            $wishing        = $basicsConfig['wishing'] ?? '';
            $actName        = $basicsConfig['act_name'] ?? '';
            $sceneId        = $basicsConfig['scene_id'] ?? '';
            $url            = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack";
            $params         = array(
                'nonce_str'    => Random::build('alnum', 16),
                'sign'         => '',
                'mch_billno'   => $orderNumber,
                'mch_id'       => $mchId,
                'wxappid'      => $appid,
                'send_name'    => $sendName,
                're_openid'    => $openid, // 用户openid
                'total_amount' => (float)$cash * 100, // 单位（分）
                'total_num'    => 1, // 红包发放总人数
                'wishing'      => $wishing,
                'client_ip'    => getClientIp(),
                'act_name'     => $actName,
                'remark'       => $actName,
                'scene_id'     => $sceneId,
            );
            $params['sign'] = strtoupper(MD5(ASCIISort($params) . '&key=' . $payKey));

            $responseXml  = curlWeChatPost($url, arrayToXml($params));
            $unifiedOrder = simplexml_load_string($responseXml, 'SimpleXMLElement', LIBXML_NOCDATA);
            $data['data'] = json_encode($unifiedOrder);
            $data         = array(
                'status'  => 3,
                'message' => '',
                'type'    => $type,
            );
            if ($unifiedOrder === false) {
                $data['code']    = 'xml error';
                $data['message'] = 'XML params error';
                // xml错误
                $row->save($data);
                return false;
            }
            if ($unifiedOrder->return_code != 'SUCCESS') {
                $data['code']    = $unifiedOrder->return_code;
                $data['message'] = $unifiedOrder->return_msg;
                $row->save($data);
                return false;
            }
            if ($unifiedOrder->result_code != 'SUCCESS') {
                $data['code']    = $unifiedOrder->err_code;
                $data['message'] = $unifiedOrder->err_code_des;
                $row->save($data);
                return false;
            }
            $data['status'] = 2;
            $data['code']   = 'SUCCESS';
            $row->save($data);
            return true;
        }
    }

    if (!function_exists('getWeChatMiniQRCode')) {
        /**
         * 获取微信小程序二维码
         * @param string $scene
         * @param string $msg
         * @return false|string
         */
        function getWeChatMiniQRCode(string $scene = '', string &$msg = '')
        {
            if (empty($scene)) {
                $msg = "小程序需传递的参数不能为空！";
                return false;
            }
            $wxConfig    = get_sys_config("", 'xcx_config');
            $appid       = $wxConfig['xcx_appid'] ?? '';
            $appSecret   = $wxConfig['xcx_secret'] ?? '';
            $tokenMsg = '';
            $accessToken = getXcxAccessToken($appid, $appSecret, $tokenMsg);
            if($accessToken === false){
                $msg = $tokenMsg;
                return false;
            }
            $url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token='.$accessToken;
            $postData = array(
                'scene' => $scene,
                'page'  => 'pages/index/index'
            );
            $result   = curlRequest($url, json_encode($postData));
            $imgData  = $result;
            $bodyData = json_decode($result, true);
            if (!empty($bodyData['errcode'])) {
                $msg = $bodyData['errmsg'] ?? '获取小程序码异常！';
                return false;
            }
            $date = date("Ymd");
            $fileDir = "/storage/default/$date/";
            if (!file_exists('.' . $fileDir)){
                // 创建目录
                mkdir('.' . $fileDir,0755,true);
            }
            $fileUrl = $fileDir . "$scene.png";
            file_put_contents('.' . $fileUrl, $imgData);
            return $fileUrl;
        }
    }

    if (!function_exists('getXcxAccessToken')) {
        /**
         * 获取小程序access_token
         */
        function getXcxAccessToken($appid = '', $appSecret = '', &$msg = '')
        {
            if (empty($appid) || empty($appSecret)) {
                $msg = '小程序参数错误！';
                return false;
            }
            $url      = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $appid . '&secret=' . $appSecret;
            $result   = curlRequest($url);
            $bodyData = json_decode($result, true);
            if (!is_array($bodyData)) {
                $msg = '请求失败！';
                return false;
            }
            if (!empty($bodyData['errcode'])) {
                $msg = $bodyData['errmsg'] ?? '获取token错误！';
                return false;
            }
            if (empty($bodyData['access_token'])) {
                $msg = 'token获取失败！';
                return false;
            }
            return $bodyData['access_token'];
        }
    }

    if (!function_exists('getJumpUrl')) {
        /**
         * 获取跳转url
         * @param $webUrl // URL
         * @param array $removeParams // 要删除的参数数组
         * @param array $addParams // 要加入的参数
         * @return string
         */
        function getJumpUrl($webUrl, array $removeParams = array(), array $addParams = array()): string
        {
            parse_str(parse_url($webUrl, PHP_URL_QUERY), $params);
            $len = count($removeParams);
            if($len > 0){
                if($len > 1){
                    foreach ($removeParams as $item){
                        unset($params[$item]);
                    }
                }else{
                    unset($params[$removeParams[0]]);
                }
            }
            if(!empty($addParams)){
                $params = array_merge($params, $addParams);
            }
            $query = http_build_query($params);
            $url   = explode("?", $webUrl)[0];
            $url   .= (!empty($query) ? '?' . $query : '');
            return $url;
        }
    }

}
